// Copyright © 2002-2010 Steve Lhomme.
// SPDX-License-Identifier: LGPL-2.1-or-later

/*!
  \file
  \author Steve Lhomme     <robux4 @ users.sf.net>
  \author Jory Stone       <jcsston @ toughguy.net>
*/
#include "ebml/EbmlCrc32.h"
#include "ebml/EbmlContexts.h"
#include "ebml/MemIOCallback.h"

#include <array>
#include <memory>

#ifdef WORDS_BIGENDIAN
static constexpr uint32_t CRC32_INDEX(uint32_t c) { return c >> 24; }
static constexpr uint32_t CRC32_SHIFTED(uint32_t c) { return c << 8; }
#else
static constexpr uint32_t CRC32_INDEX(uint32_t c) { return c & 0xFF; }
static constexpr uint32_t CRC32_SHIFTED(uint32_t c) { return c >> 8; }
#endif

static constexpr std::uint32_t CRC32_NEGL = 0xffffffffL;

namespace libebml {

static constexpr EbmlDocVersion AllEbmlVersions{};

DEFINE_EBML_CLASS_ORPHAN(EbmlCrc32, 0xBF, "EBMLCrc32", AllEbmlVersions)

static constexpr std::array<std::uint32_t, 256> s_tab {
#ifdef WORDS_BIGENDIAN
  0x00000000L, 0x96300777L, 0x2c610eeeL, 0xba510999L, 0x19c46d07L,
  0x8ff46a70L, 0x35a563e9L, 0xa395649eL, 0x3288db0eL, 0xa4b8dc79L,
  0x1ee9d5e0L, 0x88d9d297L, 0x2b4cb609L, 0xbd7cb17eL, 0x072db8e7L,
  0x911dbf90L, 0x6410b71dL, 0xf220b06aL, 0x4871b9f3L, 0xde41be84L,
  0x7dd4da1aL, 0xebe4dd6dL, 0x51b5d4f4L, 0xc785d383L, 0x56986c13L,
  0xc0a86b64L, 0x7af962fdL, 0xecc9658aL, 0x4f5c0114L, 0xd96c0663L,
  0x633d0ffaL, 0xf50d088dL, 0xc8206e3bL, 0x5e10694cL, 0xe44160d5L,
  0x727167a2L, 0xd1e4033cL, 0x47d4044bL, 0xfd850dd2L, 0x6bb50aa5L,
  0xfaa8b535L, 0x6c98b242L, 0xd6c9bbdbL, 0x40f9bcacL, 0xe36cd832L,
  0x755cdf45L, 0xcf0dd6dcL, 0x593dd1abL, 0xac30d926L, 0x3a00de51L,
  0x8051d7c8L, 0x1661d0bfL, 0xb5f4b421L, 0x23c4b356L, 0x9995bacfL,
  0x0fa5bdb8L, 0x9eb80228L, 0x0888055fL, 0xb2d90cc6L, 0x24e90bb1L,
  0x877c6f2fL, 0x114c6858L, 0xab1d61c1L, 0x3d2d66b6L, 0x9041dc76L,
  0x0671db01L, 0xbc20d298L, 0x2a10d5efL, 0x8985b171L, 0x1fb5b606L,
  0xa5e4bf9fL, 0x33d4b8e8L, 0xa2c90778L, 0x34f9000fL, 0x8ea80996L,
  0x18980ee1L, 0xbb0d6a7fL, 0x2d3d6d08L, 0x976c6491L, 0x015c63e6L,
  0xf4516b6bL, 0x62616c1cL, 0xd8306585L, 0x4e0062f2L, 0xed95066cL,
  0x7ba5011bL, 0xc1f40882L, 0x57c40ff5L, 0xc6d9b065L, 0x50e9b712L,
  0xeab8be8bL, 0x7c88b9fcL, 0xdf1ddd62L, 0x492dda15L, 0xf37cd38cL,
  0x654cd4fbL, 0x5861b24dL, 0xce51b53aL, 0x7400bca3L, 0xe230bbd4L,
  0x41a5df4aL, 0xd795d83dL, 0x6dc4d1a4L, 0xfbf4d6d3L, 0x6ae96943L,
  0xfcd96e34L, 0x468867adL, 0xd0b860daL, 0x732d0444L, 0xe51d0333L,
  0x5f4c0aaaL, 0xc97c0dddL, 0x3c710550L, 0xaa410227L, 0x10100bbeL,
  0x86200cc9L, 0x25b56857L, 0xb3856f20L, 0x09d466b9L, 0x9fe461ceL,
  0x0ef9de5eL, 0x98c9d929L, 0x2298d0b0L, 0xb4a8d7c7L, 0x173db359L,
  0x810db42eL, 0x3b5cbdb7L, 0xad6cbac0L, 0x2083b8edL, 0xb6b3bf9aL,
  0x0ce2b603L, 0x9ad2b174L, 0x3947d5eaL, 0xaf77d29dL, 0x1526db04L,
  0x8316dc73L, 0x120b63e3L, 0x843b6494L, 0x3e6a6d0dL, 0xa85a6a7aL,
  0x0bcf0ee4L, 0x9dff0993L, 0x27ae000aL, 0xb19e077dL, 0x44930ff0L,
  0xd2a30887L, 0x68f2011eL, 0xfec20669L, 0x5d5762f7L, 0xcb676580L,
  0x71366c19L, 0xe7066b6eL, 0x761bd4feL, 0xe02bd389L, 0x5a7ada10L,
  0xcc4add67L, 0x6fdfb9f9L, 0xf9efbe8eL, 0x43beb717L, 0xd58eb060L,
  0xe8a3d6d6L, 0x7e93d1a1L, 0xc4c2d838L, 0x52f2df4fL, 0xf167bbd1L,
  0x6757bca6L, 0xdd06b53fL, 0x4b36b248L, 0xda2b0dd8L, 0x4c1b0aafL,
  0xf64a0336L, 0x607a0441L, 0xc3ef60dfL, 0x55df67a8L, 0xef8e6e31L,
  0x79be6946L, 0x8cb361cbL, 0x1a8366bcL, 0xa0d26f25L, 0x36e26852L,
  0x95770cccL, 0x03470bbbL, 0xb9160222L, 0x2f260555L, 0xbe3bbac5L,
  0x280bbdb2L, 0x925ab42bL, 0x046ab35cL, 0xa7ffd7c2L, 0x31cfd0b5L,
  0x8b9ed92cL, 0x1daede5bL, 0xb0c2649bL, 0x26f263ecL, 0x9ca36a75L,
  0x0a936d02L, 0xa906099cL, 0x3f360eebL, 0x85670772L, 0x13570005L,
  0x824abf95L, 0x147ab8e2L, 0xae2bb17bL, 0x381bb60cL, 0x9b8ed292L,
  0x0dbed5e5L, 0xb7efdc7cL, 0x21dfdb0bL, 0xd4d2d386L, 0x42e2d4f1L,
  0xf8b3dd68L, 0x6e83da1fL, 0xcd16be81L, 0x5b26b9f6L, 0xe177b06fL,
  0x7747b718L, 0xe65a0888L, 0x706a0fffL, 0xca3b0666L, 0x5c0b0111L,
  0xff9e658fL, 0x69ae62f8L, 0xd3ff6b61L, 0x45cf6c16L, 0x78e20aa0L,
  0xeed20dd7L, 0x5483044eL, 0xc2b30339L, 0x612667a7L, 0xf71660d0L,
  0x4d476949L, 0xdb776e3eL, 0x4a6ad1aeL, 0xdc5ad6d9L, 0x660bdf40L,
  0xf03bd837L, 0x53aebca9L, 0xc59ebbdeL, 0x7fcfb247L, 0xe9ffb530L,
  0x1cf2bdbdL, 0x8ac2bacaL, 0x3093b353L, 0xa6a3b424L, 0x0536d0baL,
  0x9306d7cdL, 0x2957de54L, 0xbf67d923L, 0x2e7a66b3L, 0xb84a61c4L,
  0x021b685dL, 0x942b6f2aL, 0x37be0bb4L, 0xa18e0cc3L, 0x1bdf055aL,
  0x8def022dL
#else
  0x00000000L, 0x77073096L, 0xee0e612cL, 0x990951baL, 0x076dc419L,
  0x706af48fL, 0xe963a535L, 0x9e6495a3L, 0x0edb8832L, 0x79dcb8a4L,
  0xe0d5e91eL, 0x97d2d988L, 0x09b64c2bL, 0x7eb17cbdL, 0xe7b82d07L,
  0x90bf1d91L, 0x1db71064L, 0x6ab020f2L, 0xf3b97148L, 0x84be41deL,
  0x1adad47dL, 0x6ddde4ebL, 0xf4d4b551L, 0x83d385c7L, 0x136c9856L,
  0x646ba8c0L, 0xfd62f97aL, 0x8a65c9ecL, 0x14015c4fL, 0x63066cd9L,
  0xfa0f3d63L, 0x8d080df5L, 0x3b6e20c8L, 0x4c69105eL, 0xd56041e4L,
  0xa2677172L, 0x3c03e4d1L, 0x4b04d447L, 0xd20d85fdL, 0xa50ab56bL,
  0x35b5a8faL, 0x42b2986cL, 0xdbbbc9d6L, 0xacbcf940L, 0x32d86ce3L,
  0x45df5c75L, 0xdcd60dcfL, 0xabd13d59L, 0x26d930acL, 0x51de003aL,
  0xc8d75180L, 0xbfd06116L, 0x21b4f4b5L, 0x56b3c423L, 0xcfba9599L,
  0xb8bda50fL, 0x2802b89eL, 0x5f058808L, 0xc60cd9b2L, 0xb10be924L,
  0x2f6f7c87L, 0x58684c11L, 0xc1611dabL, 0xb6662d3dL, 0x76dc4190L,
  0x01db7106L, 0x98d220bcL, 0xefd5102aL, 0x71b18589L, 0x06b6b51fL,
  0x9fbfe4a5L, 0xe8b8d433L, 0x7807c9a2L, 0x0f00f934L, 0x9609a88eL,
  0xe10e9818L, 0x7f6a0dbbL, 0x086d3d2dL, 0x91646c97L, 0xe6635c01L,
  0x6b6b51f4L, 0x1c6c6162L, 0x856530d8L, 0xf262004eL, 0x6c0695edL,
  0x1b01a57bL, 0x8208f4c1L, 0xf50fc457L, 0x65b0d9c6L, 0x12b7e950L,
  0x8bbeb8eaL, 0xfcb9887cL, 0x62dd1ddfL, 0x15da2d49L, 0x8cd37cf3L,
  0xfbd44c65L, 0x4db26158L, 0x3ab551ceL, 0xa3bc0074L, 0xd4bb30e2L,
  0x4adfa541L, 0x3dd895d7L, 0xa4d1c46dL, 0xd3d6f4fbL, 0x4369e96aL,
  0x346ed9fcL, 0xad678846L, 0xda60b8d0L, 0x44042d73L, 0x33031de5L,
  0xaa0a4c5fL, 0xdd0d7cc9L, 0x5005713cL, 0x270241aaL, 0xbe0b1010L,
  0xc90c2086L, 0x5768b525L, 0x206f85b3L, 0xb966d409L, 0xce61e49fL,
  0x5edef90eL, 0x29d9c998L, 0xb0d09822L, 0xc7d7a8b4L, 0x59b33d17L,
  0x2eb40d81L, 0xb7bd5c3bL, 0xc0ba6cadL, 0xedb88320L, 0x9abfb3b6L,
  0x03b6e20cL, 0x74b1d29aL, 0xead54739L, 0x9dd277afL, 0x04db2615L,
  0x73dc1683L, 0xe3630b12L, 0x94643b84L, 0x0d6d6a3eL, 0x7a6a5aa8L,
  0xe40ecf0bL, 0x9309ff9dL, 0x0a00ae27L, 0x7d079eb1L, 0xf00f9344L,
  0x8708a3d2L, 0x1e01f268L, 0x6906c2feL, 0xf762575dL, 0x806567cbL,
  0x196c3671L, 0x6e6b06e7L, 0xfed41b76L, 0x89d32be0L, 0x10da7a5aL,
  0x67dd4accL, 0xf9b9df6fL, 0x8ebeeff9L, 0x17b7be43L, 0x60b08ed5L,
  0xd6d6a3e8L, 0xa1d1937eL, 0x38d8c2c4L, 0x4fdff252L, 0xd1bb67f1L,
  0xa6bc5767L, 0x3fb506ddL, 0x48b2364bL, 0xd80d2bdaL, 0xaf0a1b4cL,
  0x36034af6L, 0x41047a60L, 0xdf60efc3L, 0xa867df55L, 0x316e8eefL,
  0x4669be79L, 0xcb61b38cL, 0xbc66831aL, 0x256fd2a0L, 0x5268e236L,
  0xcc0c7795L, 0xbb0b4703L, 0x220216b9L, 0x5505262fL, 0xc5ba3bbeL,
  0xb2bd0b28L, 0x2bb45a92L, 0x5cb36a04L, 0xc2d7ffa7L, 0xb5d0cf31L,
  0x2cd99e8bL, 0x5bdeae1dL, 0x9b64c2b0L, 0xec63f226L, 0x756aa39cL,
  0x026d930aL, 0x9c0906a9L, 0xeb0e363fL, 0x72076785L, 0x05005713L,
  0x95bf4a82L, 0xe2b87a14L, 0x7bb12baeL, 0x0cb61b38L, 0x92d28e9bL,
  0xe5d5be0dL, 0x7cdcefb7L, 0x0bdbdf21L, 0x86d3d2d4L, 0xf1d4e242L,
  0x68ddb3f8L, 0x1fda836eL, 0x81be16cdL, 0xf6b9265bL, 0x6fb077e1L,
  0x18b74777L, 0x88085ae6L, 0xff0f6a70L, 0x66063bcaL, 0x11010b5cL,
  0x8f659effL, 0xf862ae69L, 0x616bffd3L, 0x166ccf45L, 0xa00ae278L,
  0xd70dd2eeL, 0x4e048354L, 0x3903b3c2L, 0xa7672661L, 0xd06016f7L,
  0x4969474dL, 0x3e6e77dbL, 0xaed16a4aL, 0xd9d65adcL, 0x40df0b66L,
  0x37d83bf0L, 0xa9bcae53L, 0xdebb9ec5L, 0x47b2cf7fL, 0x30b5ffe9L,
  0xbdbdf21cL, 0xcabac28aL, 0x53b39330L, 0x24b4a3a6L, 0xbad03605L,
  0xcdd70693L, 0x54de5729L, 0x23d967bfL, 0xb3667a2eL, 0xc4614ab8L,
  0x5d681b02L, 0x2a6f2b94L, 0xb40bbe37L, 0xc30c8ea1L, 0x5a05df1bL,
  0x2d02ef8dL
#endif
};

EbmlCrc32::EbmlCrc32()
  : EbmlBinary(EbmlCrc32::ClassInfos)
{
  ResetCRC();
  SetDefaultSize(4);
  SetSize_(4);
  //This EbmlElement has been set
  //  SetValueIsSet();
}

void EbmlCrc32::ResetCRC()
{
  m_crc = CRC32_NEGL;
}

void EbmlCrc32::UpdateByte(binary b)
{
  m_crc = s_tab.at(CRC32_INDEX(m_crc) ^ b) ^ CRC32_SHIFTED(m_crc);
}

void EbmlCrc32::AddElementCRC32(EbmlElement &ElementToCRC)
{
  // Use a special IOCallback class that Render's to memory instead of to disk
  MemIOCallback memoryBuffer;
  ElementToCRC.Render(memoryBuffer, WriteAll, true);

  const std::uint64_t memSize = memoryBuffer.GetDataBufferSize();
  if (memSize > std::numeric_limits<std::uint32_t>::max())
    return;

  Update(memoryBuffer.GetDataBuffer(), static_cast<std::uint32_t>(memSize));
  //  Finalize();
}

bool EbmlCrc32::CheckElementCRC32(EbmlElement &ElementToCRC) const
{
  MemIOCallback memoryBuffer;
  ElementToCRC.Render(memoryBuffer);

  const std::uint64_t memSize = memoryBuffer.GetDataBufferSize();
  if (memSize > std::numeric_limits<std::uint32_t>::max())
    return false;

  return CheckCRC(m_crc_final, memoryBuffer.GetDataBuffer(), static_cast<std::uint32_t>(memSize));
}

filepos_t EbmlCrc32::RenderData(IOCallback & output, bool /* bForceRender */, const ShouldWrite & /* writeFilter */)
{
  filepos_t Result = 4;

  if (Result != 0) {
    output.writeFully(&m_crc_final, Result);
  }

  if (Result < GetDefaultSize()) {
    // pad the rest with 0
    auto Pad = std::make_unique<binary>(GetDefaultSize() - Result);
    if (Pad != nullptr) {
      output.writeFully(Pad.get(), GetDefaultSize() - Result);

      Result = GetDefaultSize();
    }
  }

  return Result;
}

filepos_t EbmlCrc32::ReadData(IOCallback & input, ScopeMode ReadFully)
{
  if (ReadFully == SCOPE_NO_DATA)
    return GetSize();

  if (GetSize() != 4) {
    // impossible to read, skip it
    input.setFilePointer(GetSize(), seek_current);
    return GetSize();
  }

  input.readFully(&m_crc_final, GetSize());
  SetValueIsSet();

  return GetSize();
}

bool EbmlCrc32::CheckCRC(std::uint32_t inputCRC, const binary *input, std::uint32_t length)
{
  std::uint32_t crc = CRC32_NEGL;

  for(; !IsAligned<std::uint32_t>(input) && length > 0; length--)
    crc = s_tab.at(CRC32_INDEX(crc) ^ *input++) ^ CRC32_SHIFTED(crc);

  while (length >= 4) {
    auto buf = reinterpret_cast<const std::uint8_t *>(input);
    std::uint32_t chunk{};
    for (int idx = 3; idx >= 0; --idx)
      chunk = (chunk << 8) | buf[idx];
    crc ^= chunk;
    crc = s_tab.at(CRC32_INDEX(crc)) ^ CRC32_SHIFTED(crc);
    crc = s_tab.at(CRC32_INDEX(crc)) ^ CRC32_SHIFTED(crc);
    crc = s_tab.at(CRC32_INDEX(crc)) ^ CRC32_SHIFTED(crc);
    crc = s_tab.at(CRC32_INDEX(crc)) ^ CRC32_SHIFTED(crc);
    length -= 4;
    input += 4;
  }

  while (length--)
    crc = s_tab.at(CRC32_INDEX(crc) ^ *input++) ^ CRC32_SHIFTED(crc);

  //Now we finalize the CRC32
  crc ^= CRC32_NEGL;

  return crc == inputCRC;
}

void EbmlCrc32::FillCRC32(const binary *input, std::uint32_t length)
{
  ResetCRC();
  Update(input, length);
  Finalize();

  /*std::uint32_t crc = CRC32_NEGL;

  for(; !IsAligned<std::uint32_t>(s) && n > 0; n--)
    crc = s_tab.at(CRC32_INDEX(crc) ^ *s++) ^ CRC32_SHIFTED(crc);

  while (n >= 4)
  {
    crc ^= *(const std::uint32_t *)s;
    crc = s_tab.at(CRC32_INDEX(crc)) ^ CRC32_SHIFTED(crc);
    crc = s_tab.at(CRC32_INDEX(crc)) ^ CRC32_SHIFTED(crc);
    crc = s_tab.at(CRC32_INDEX(crc)) ^ CRC32_SHIFTED(crc);
    crc = s_tab.at(CRC32_INDEX(crc)) ^ CRC32_SHIFTED(crc);
    n -= 4;
    s += 4;
  }

  while (n--)
    crc = s_tab.at(CRC32_INDEX(crc) ^ *s++) ^ CRC32_SHIFTED(crc);

  m_crc = crc;

  //Now we finalize the CRC32
  m_crc ^= CRC32_NEGL;
  //for (unsigned int i = 0; i < 4; i++)
  //  (&last_crc32)[i] = GetCrcByte(i);*/

}

void EbmlCrc32::Update(const binary *input, std::uint32_t length)
{
  std::uint32_t crc = m_crc;

  for(; !IsAligned<std::uint32_t>(input) && length > 0; length--)
    crc = s_tab.at(CRC32_INDEX(crc) ^ *input++) ^ CRC32_SHIFTED(crc);

  while (length >= 4) {
    auto buf = reinterpret_cast<const std::uint8_t *>(input);
    std::uint32_t chunk{};
    for (int idx = 3; idx >= 0; --idx)
      chunk = (chunk << 8) | buf[idx];
    crc ^= chunk;
    crc = s_tab.at(CRC32_INDEX(crc)) ^ CRC32_SHIFTED(crc);
    crc = s_tab.at(CRC32_INDEX(crc)) ^ CRC32_SHIFTED(crc);
    crc = s_tab.at(CRC32_INDEX(crc)) ^ CRC32_SHIFTED(crc);
    crc = s_tab.at(CRC32_INDEX(crc)) ^ CRC32_SHIFTED(crc);
    length -= 4;
    input += 4;
  }

  while (length--)
    crc = s_tab.at(CRC32_INDEX(crc) ^ *input++) ^ CRC32_SHIFTED(crc);

  m_crc = crc;
}

void EbmlCrc32::Finalize()
{
  //Finalize the CRC32
  m_crc ^= CRC32_NEGL;
  //Copy it over to completed CRC32 memeber
  m_crc_final = m_crc;
  //Reset the holding CRC member (m_crc)
  ResetCRC();
  //This EbmlElement has been set
  SetValueIsSet();
}

} // namespace libebml
